﻿using System;
using System.Reflection;
using Fine.Utils.Functions;
using JetBrains.Annotations;


namespace Crocodile.LangUtils
{
    /// <summary>
    /// Provides common access to fields and properties.
    /// </summary>
    public abstract class DataMemberInfo : MemberInfo
    {
        public abstract Type DataType { get; }


        public abstract void SetValue<T>(object obj, T value);


        public abstract T GetValue<T>(object obj);


        public abstract string KindName { get; }


        public override string ToString()
        {
            return KindName + ' ' + Name;
        }
    }


    public class FieldMemeberInfo : DataMemberInfo
    {
        [NotNull] 
        private readonly FieldInfo fi;


        public FieldMemeberInfo([NotNull] FieldInfo fi)
        {
            this.fi = fi;
        }


        public override Type DataType
        {
            get { return fi.FieldType; }
        }


        public override void SetValue<T>(object obj, T value)
        {
            fi.SetValue(obj, value);
        }


        public override T GetValue<T>(object obj)
        {
            var v = fi.GetValue(obj);
            if (v == null)
                return (T)v;

            if (v is T)
                return (T)v;
            else
                throw new /*UnsupportedType*/Exception(
                    "Access to field {0}: rerquested type {1} but the real type id {2}."
                    .With(fi.Name, typeof(T).Name, v.GetType().Name));
        }



        #region DELEGATES

        public override object[] GetCustomAttributes(bool inherit)
        {
            return fi.GetCustomAttributes(inherit);
        }


        public override object[] GetCustomAttributes(Type attributeType, bool inherit)
        {
            return fi.GetCustomAttributes(attributeType, inherit);
        }


        public override bool IsDefined(Type attributeType, bool inherit)
        {
            return fi.IsDefined(attributeType, inherit);
        }


        public override MemberTypes MemberType
        {
            get { return fi.MemberType; }
        }

        public override string Name
        {
            get { return fi.Name; }
        }

        public override Type DeclaringType
        {
            get { return fi.DeclaringType; }
        }

        public override Type ReflectedType
        {
            get { return fi.ReflectedType; }
        }

        #endregion



        public override string KindName
        {
            get { return "field"; }
        }



        #region EQUALITY

        public bool Equals(FieldMemeberInfo other)
        {
            if (ReferenceEquals(null, other)) return false;
            if (ReferenceEquals(this, other)) return true;
            return base.Equals(other) && Equals(other.fi, fi);
        }


        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            return Equals(obj as FieldMemeberInfo);
        }


        public override int GetHashCode()
        {
            return fi.GetHashCode();
        }

        #endregion
    }


    public class PropertyMemeberInfo : DataMemberInfo
    {
        [NotNull] 
        private readonly PropertyInfo pi;


        public PropertyMemeberInfo([NotNull] PropertyInfo pi)
        {
            this.pi = pi;
        }


        public override Type DataType
        {
            get { return pi.PropertyType; }
        }


        public override void SetValue<T>(object obj, T value)
        {
            pi.SetValue(obj, value, index: null);
        }


        public override T GetValue<T>(object obj)
        {
            var v = pi.GetValue(obj, index: null);
            if (v == null)
                return (T)v;

            if (v is T)
                return (T)v;
            else
                throw new /*UnsupportedType*/Exception(
                    "Access to property {0}: rerquested type {1} but the real type id {2}."
                    .With(pi.Name, typeof(T).Name, v.GetType().Name));
        }



        #region DELEGATES

        public override object[] GetCustomAttributes(bool inherit)
        {
            return pi.GetCustomAttributes(inherit);
        }


        public override object[] GetCustomAttributes(Type attributeType, bool inherit)
        {
            return pi.GetCustomAttributes(attributeType, inherit);
        }


        public override bool IsDefined(Type attributeType, bool inherit)
        {
            return pi.IsDefined(attributeType, inherit);
        }


        public override MemberTypes MemberType
        {
            get { return pi.MemberType; }
        }

        public override string Name
        {
            get { return pi.Name; }
        }

        public override Type DeclaringType
        {
            get { return pi.DeclaringType; }
        }

        public override Type ReflectedType
        {
            get { return pi.ReflectedType; }
        }

        #endregion



        public override string KindName
        {
            get { return "property"; }
        }


        #region EQUALITY    

        public bool Equals(PropertyMemeberInfo other)
        {
            if (ReferenceEquals(null, other)) return false;
            if (ReferenceEquals(this, other)) return true;
            return base.Equals(other) && Equals(other.pi, pi);
        }


        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            return Equals(obj as PropertyMemeberInfo);
        }


        public override int GetHashCode()
        {
            return pi.GetHashCode();
        }

        #endregion
    }



}
